import * as mysql from 'mysql';
import {IPool} from "mysql";
import {IPoolConfig} from "mysql";
import {IUpdateRes, IQueryRes, ISql, sql} from "./Isql";
import {Log} from "../Log";
import {IConnection} from "mysql";
import {SqlBuilder} from "./SqlBuilder";
import {sys} from "../sys";

export interface updateRes {
    insertId?: any

    /**
     * delete 结果
     */
    affectedRows?: number

    /**
     * UPDATE结果
     */
    changedRows?: number
}

export class mysqlTransaction implements ISql {
    constructor(private conn: IConnection, private config: IPoolConfig&mysqlConfig) {
    }

    update(sqlStr: string, res: (resault: IUpdateRes) => void) {
        mysqlConnecter.conUpdate(this.config, this.conn, sqlStr, res, true, new Error());
    }


    query(sqlStr: string, res: (rows: IQueryRes<any[]>) => void) {
        mysqlConnecter.conQuery(this.config, this.conn, sqlStr, res, true, new Error());
    }


    getConnectName() {
        return "" + this.config.host + ":" + this.config.port + "-" + this.config.database + "_";
    }

    getMaxCache() {
        if (!this.config.maxCache)
            return 10000;
        return this.config.maxCache;
    }

    sql<TC>(sql: SqlBuilder<TC>): SqlBuilder<TC> {
        sql.sqlCon = this;
        return sql;
    }

}

export interface mysqlConfig {
    /**
     * sql错误日志
     */
    sqlErrorLog?: boolean;
    /**
     * 所有sql查询日志
     */
    sqlQueryLog?: boolean;

    /**
     * 所有查询结果日志
     */
    sqlQueryResultLog?: boolean
    /**
     * 所有sql更新日志
     */
    sqlUpdateLog?: boolean;

    /**
     * 慢查询日志,记录大于此时间(毫秒)的sql
     */
    sqlSlowLog?: number;

    /**
     * 每个表的最大对象缓存数量,缺省10000
     */
    maxCache?: number;
}


export class mysqlConnecter implements ISql {


    constructor(public config: IPoolConfig&mysqlConfig, private pool: IPool = mysql.createPool(config)) {
        if (config.maxCache === void 0) {
            config.maxCache = 10000;
        }
    }


    getMaxCache() {
        if (!this.config.maxCache)
            return 10000;

        return this.config.maxCache;
    }

    getConnectName() {
        return this.config.host + "" + this.config.port + this.config.database;
    }

    static sqlFilter(str: any): string {
        return sql.filter(str);
    }


    static getUpdateRes() {
        return <IUpdateRes>{
            affectCount: 0,
            toString(){
                return JSON.stringify(this);
            }
        }
    }

    static conUpdate(config: IPoolConfig&mysqlConfig, conn: IConnection, sqlStr: string, res: ((resault: IUpdateRes) => void)|undefined
        , notRelease: boolean
        , errStack: Error) {
        conn.query(sqlStr, (err, result: updateRes)=> {
            var update = mysqlConnecter.getUpdateRes();
            if (err) {
                if (config.sqlErrorLog) {
                    errStack.message = err.message
                    errStack.name = err.name;
                    Log.write("update error:\r\n" + sqlStr, errStack);
                }
                update.err = err;
            }

            if (result) {
                if (result.affectedRows)
                    update.affectCount = result.affectedRows;

                if (result.changedRows)
                    update.affectCount = result.changedRows;

                if (result.insertId)
                    update.insertId = result.insertId;
            }

            if (!notRelease)
                conn.release();

            if (config.sqlUpdateLog && !err) {
                Log.write(sqlStr + "\r\n" + update);
            }

            if (res)
                res(update);
        });
    }

    update(sqlStr: string, res?: (resault: IUpdateRes) => void) {
        var queryStack = new Error();

        this.pool.getConnection((err, conn)=> {
            if (err) {
                if (this.config.sqlErrorLog) {
                    Log.write("POOL getConnection error:\r\n" + sqlStr, err);
                }
                var update = mysqlConnecter.getUpdateRes();
                update.err = err;
                if (res)
                    res(update);
                return;
            }
            mysqlConnecter.conUpdate(this.config, conn, sqlStr, res, false, queryStack);

        });
    }

    static conQuery(config: IPoolConfig&mysqlConfig, conn: IConnection, sqlStr: string
        , res: (rows: IQueryRes<any[]>) => void
        , notRelease: boolean, errStack: Error) {

        if (config.sqlSlowLog !== void 0) {
            var lastTime = sys.getPreciseTime();
        }

        conn.query(sqlStr, (err, rows)=> {
            var datas: IQueryRes<any[]> = {
                toString(){
                    return JSON.stringify(this);
                }
            }

            if (err) {
                if (config.sqlErrorLog) {
                    errStack.message = err.message
                    errStack.name = err.name;
                    Log.write("query  error:\r\n" + sqlStr, errStack);
                }
                datas.err = err;
            }
            else {
                if (config.sqlSlowLog !== void 0) {
                    let diffTime = lastTime.getMilli();
                    if (diffTime >= config.sqlSlowLog) {
                        errStack.message = "sql慢查询";
                        errStack.name = "slow sql";
                        Log.write("sql执行时长:" + diffTime + "毫秒:\r\n" + sqlStr, errStack);
                    }
                }
            }


            if (rows)
                datas.data = rows;

            if (config.sqlQueryLog && !err) {
                if (config.sqlQueryResultLog)
                    Log.write(sqlStr + "\r\n" + datas);
                else
                    Log.write(sqlStr);
            }

            if (!notRelease)
                conn.release();
            res(datas);

        });
    }

    query(sqlStr: string, res: (rows: IQueryRes<any[]>) => void) {
        var queryStack = new Error();

        this.pool.getConnection((err, conn) => {
            if (err) {
                var datas: IQueryRes<any[]> = {
                    toString(){
                        return JSON.stringify(this);
                    }
                }
                if (this.config.sqlErrorLog)
                    Log.write("POOL getConnection error:\r\n" + sqlStr, err);
                datas.err = err;
                res(datas);
                return;
            }

            mysqlConnecter.conQuery(this.config, conn, sqlStr, res, false, queryStack);

        });
    }


    transaction(func: (tra: mysqlTransaction)=>Promise<void>): Promise<void> {
        return new Promise<void>((resolve, reject)=> {
            this.pool.getConnection((err, conn)=> {
                if (err) {
                    if (this.config.sqlErrorLog) {
                        Log.write("transaction getConnection error", err);
                    }
                    reject(err);
                    return;
                }
                conn.beginTransaction(err=> {
                    if (err) {
                        if (this.config.sqlErrorLog)
                            Log.write("beginTransaction error", err);
                        reject(err);
                        return;
                    }

                    let tra = new mysqlTransaction(conn, this.config);
                    func(tra).then(()=> {
                        conn.commit(err=> {
                            try {
                                conn.release();
                            } catch (e) {
                                if (this.config.sqlErrorLog)
                                    Log.write("conn.release error", e);
                            }
                            if (err) {
                                if (this.config.sqlErrorLog)
                                    Log.write("beginTransaction commit error", err);
                                reject(err);
                                return;
                            }

                            //执行成功
                            resolve();
                        });
                    }).catch(err=> {
                        if (this.config.sqlErrorLog)
                            Log.write("beginTransaction catch error", err);
                        conn.rollback(()=> {
                            try {
                                conn.release();
                            } catch (e) {
                                if (this.config.sqlErrorLog)
                                    Log.write("conn.release error", e);
                            }
                        });
                        reject(err);
                    });
                });
            });
        });
    }


    /////////class end
}
